/**
 * 
 */
package truerefactor;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import truerefactor.codesmell.CodeSmell;
import truerefactor.codesmell.LargeClass;
import truerefactor.codesmell.LazyClass;
import truerefactor.codesmell.LongMethod;
import truerefactor.codesmell.ShotgunSurgery;
import truerefactor.codesmell.TemporaryField;
import truerefactor.genetic.GAParameters;
import truerefactor.genetic.GeneticAlgorithm;
import truerefactor.graph.CodeGraph;
import truerefactor.metrics.CBO;
import truerefactor.metrics.CS;
import truerefactor.metrics.DIT;
import truerefactor.metrics.LCOM;
import truerefactor.metrics.Metric;
import truerefactor.metrics.NOC;
import truerefactor.metrics.RFC;
import truerefactor.metrics.WMC;
import truerefactor.refactor.DecomposeConditional;
import truerefactor.refactor.DelegationToInheritance;
import truerefactor.refactor.DuplicateObservedData;
import truerefactor.refactor.EncapsulateField;
import truerefactor.refactor.ExtractClass;
import truerefactor.refactor.ExtractInterface;
import truerefactor.refactor.ExtractSubClass;
import truerefactor.refactor.InlineClass;
import truerefactor.refactor.InlineMethod;
import truerefactor.refactor.InlineTemp;
import truerefactor.refactor.IntroduceNullObject;
import truerefactor.refactor.IntroduceParamObject;
import truerefactor.refactor.MoveField;
import truerefactor.refactor.MoveMethod;
import truerefactor.refactor.PreserveWholeObject;
import truerefactor.refactor.PullUpField;
import truerefactor.refactor.PullUpMethod;
import truerefactor.refactor.PushDownField;
import truerefactor.refactor.PushDownMethod;
import truerefactor.refactor.Refactoring;
import truerefactor.refactor.RemoveMiddleMan;
import truerefactor.refactor.RemoveSettingMethod;
import truerefactor.refactor.ReplaceConditionalWithPolymorphism;
import truerefactor.refactor.ReplaceMethodWithMethodObject;
import truerefactor.refactor.ReplaceTempWithQuery;
import truerefactor.refactor.SelfEncapsulateField;
import truerefactor.refactor.SubstituteAlgorithm;

/**
 * RefactoringController - The class that passes information between the
 * refactoring system and the system controller. It also maintains the
 * Responsibility of controlling the refactoring system and providing needed
 * information from other sources to the refactoring system on demand.
 * 
 * @author Isaac Griffith
 */
public class RefactoringController {

    /**
     * The current code graph
     */
    private CodeGraph model;
    /**
     * the list of usable metrics
     */
    private List<Metric> metrics;
    /**
     * the list of usable refactorings
     */
    private List<Refactoring> refactorings;
    /**
     * the list of all known refactoring sequences
     */
    private List<List<Refactoring>> sequences;
    /**
     * the list of usable code smells
     */
    private List<CodeSmell> smells;
    /**
     * the genetic algorithm used to find the best sequence of refactoring
     * sequences
     */
    private GeneticAlgorithm genAlg;
    /**
     * the parent system controller
     */
    private SystemController control;
    /**
     * the current generation for logging
     */
    private int currentLogGeneration;

    /**
     * Creates a new RefactoringController instance
     * 
     * @param control
     *            parent system controller
     * @param model
     *            initial code graph
     */
    public RefactoringController(SystemController control, CodeGraph model)
    {
        this.control = control;
        this.model = model;
        metrics = new ArrayList<Metric>();
        // refactorings = new ArrayList<Refactoring>();
        smells = new ArrayList<CodeSmell>();
        sequences = new LinkedList<List<Refactoring>>();

        initMetrics();
        initRefactoringCommands();
        initSmells();
    }

    /**
     * Uses reflection to find known Classes that extend
     * truerefactor.metrics.Metric and then instantiates an object of each and
     * adds it to the metrics list.
     */
    private void initMetrics()
    {
        metrics = new ArrayList<Metric>();
        metrics.add(new CBO());
        metrics.add(new LCOM());
        metrics.add(new WMC());
        metrics.add(new DIT());
        metrics.add(new RFC());
        metrics.add(new NOC());
        metrics.add(new CS());
    }

    /**
     * Uses reflection to find known Classes that extend
     * truerefactor.refactor.Refactoring and then instantiates an object of each
     * and adds them to the command list.
     */
    private void initRefactoringCommands()
    {
        refactorings = new ArrayList<Refactoring>();
        refactorings.add(new DelegationToInheritance());
        refactorings.add(new DecomposeConditional());
        refactorings.add(new DuplicateObservedData());
        refactorings.add(new EncapsulateField());
        refactorings.add(new ExtractClass());
        refactorings.add(new ExtractInterface());
        refactorings.add(new ExtractSubClass());
        refactorings.add(new InlineClass());
        refactorings.add(new InlineMethod());
        refactorings.add(new InlineTemp());
        refactorings.add(new IntroduceNullObject());
        refactorings.add(new IntroduceParamObject());
        refactorings.add(new MoveField());
        refactorings.add(new MoveMethod());
        refactorings.add(new PreserveWholeObject());
        refactorings.add(new PullUpField());
        refactorings.add(new PullUpMethod());
        refactorings.add(new PushDownField());
        refactorings.add(new PushDownMethod());
        refactorings.add(new RemoveMiddleMan());
        refactorings.add(new RemoveSettingMethod());
        refactorings.add(new ReplaceConditionalWithPolymorphism());
        refactorings.add(new ReplaceMethodWithMethodObject());
        refactorings.add(new ReplaceTempWithQuery());
        refactorings.add(new SelfEncapsulateField());
        refactorings.add(new SubstituteAlgorithm());
    }

    /**
     * Uses reflection to find known Classes that extend
     * truerefactor.refactor.CodeSmell and then instantiates an object of each
     * and adds them to the smells list
     */
    private void initSmells()
    {
        smells = new ArrayList<CodeSmell>();
        smells.add(new LazyClass());
        smells.add(new LongMethod());
        smells.add(new LargeClass());
        smells.add(new ShotgunSurgery());
        smells.add(new TemporaryField());
    }

    /**
     * Method designated to find, initialize, and return an instance of a
     * Refactoring to be stored within an allele and later be used.
     * 
     * @return the newly generated refactoring command
     */
    public Refactoring getPossibleRefactoring()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @return a list of the names of all available refactorings
     */
    public List<String> getAvailableRefactorings()
    {
        List<String> names = new ArrayList<String>();
        for (Refactoring cmd : refactorings)
        {
            names.add(cmd.getName());
        }

        return names;
    }

    /**
     * @return a list of the names of all available metrics
     */
    public List<String> getAvailableMetrics()
    {
        List<String> names = new ArrayList<String>();
        for (Metric metric : metrics)
        {
            names.add(metric.getName());
        }

        return names;
    }

    /**
     * @return a list of the names of all available code smell detection
     *         algorithms
     */
    public List<String> getAvailableCodeSmells()
    {
        List<String> names = new ArrayList<String>();

        for (CodeSmell smell : smells)
        {
            names.add(smell.getName());
        }

        return names;
    }

    /**
     * Starts the refactoring process
     * 
     * @param graph
     *            initial code graph
     * @param params
     *            initialization parameters for the GA
     */
    public void start(CodeGraph graph, GAParameters params)
    {
        this.model = graph;

        genAlg = new GeneticAlgorithm(this, params, sequences);

        // ExecutorService service = Executors.newSingleThreadExecutor();
        // service.execute(genAlg);
        CodeGraph bestGraph = genAlg.runGP();
        control.refactoringComplete("Refactoring Complete", bestGraph);
    }

    /**
     * @return List of currently available metrics
     */
    public List<Metric> getMetrics()
    {
        return metrics;
    }

    /**
     * @return List of currently available code smells
     */
    public List<CodeSmell> getSmells()
    {
        return smells;
    }

    /**
     * Records a new heading in the log
     * 
     * @param generation
     *            current logging generation
     * @param heading
     *            the heading
     */
    public synchronized void recordHeading(int generation, String heading)
    {
        control.updateLog("\nInformation for generation: " + generation);
        control.updateLog("\n" + heading + "\n");

        currentLogGeneration = generation;
    }

    /**
     * records and individual code smell value into the log
     * 
     * @param smellName
     *            name of the code smell
     * @param value
     *            the actual value
     * @param isFirst
     *            currently does nothing
     */
    public synchronized void recordIndividualSmell(String smellName, double value, boolean isFirst)
    {
        if (isFirst)
        {
            control.updateLog(String.format("%10.5f,", value));
        }
        else
        {
            control.updateLog(String.format("%10.5f,", value));
        }
    }

    /**
     * Records a total code smell value
     * 
     * @param value
     *            the total code smell value
     */
    public synchronized void recordTotalSmell(double value)
    {
        control.updateLog(String.format("%10.5f,", value));
    }

    /**
     * records a code smell specific heading
     * 
     * @param generation
     *            generation for the code smell measurements
     * @param heading
     *            the heading to record
     */
    public synchronized void recordCodeSmellHeading(int generation, String heading)
    {
        control.updateLog("\n\n" + heading);
    }

    /**
     * records a fitness specific heading
     * 
     * @param generation
     *            generation for the fitness measurements
     * @param heading
     *            the heading to record
     */
    public synchronized void recordFitnessHeading(int generation, String heading)
    {
        currentLogGeneration = generation;
        control.updateLog("\n" + heading);
    }

    /**
     * Records an individual fitness metric
     * 
     * @param generation
     *            current generation
     * @param metric
     *            name of the metric
     * @param fitness
     *            measured value
     * @param isFirst
     *            flagto control formatting
     */
    public synchronized void recordIndividualFitnessMetric(int generation, String metric, double fitness,
            boolean isFirst)
    {
        if (isFirst)
        {
            control.updateLog("\n" + String.format("%10.5f,", fitness));
        }
        else
        {
            control.updateLog(String.format("%10.5f,", fitness));
        }
    }

    /**
     * Records the best fitness found for the current generation
     * 
     * @param generation
     *            log generation
     * @param fitness
     *            best total fitness value
     */
    public synchronized void recordBestTotalFitness(int generation, double fitness)
    {
        if (generation == currentLogGeneration)
        {
            control.updateLog("\n   -------------------------------------------------------------------------------------\n"
                    + "   Best Total Fitness = " + fitness);
        }
    }

    /**
     * Sets the graph currently in use
     * 
     * @param graph
     *            graph to be used
     */
    public synchronized void setGraph(CodeGraph graph)
    {
        this.model = graph;
        control.setGraph(graph);
    }

    /**
     * notifies the refactoring controller that the GA is finished
     * 
     * @param graph
     *            graph repsentation after the best sequence of refactorings has
     *            been applied
     */
    public void finish(CodeGraph graph)
    {
        setGraph(graph);
        control.refactoringComplete("Refactoring completed", graph);
    }

    /**
     * notifies the system controller to notify the gui that the logging panel
     * needs to be purged
     */
    public void purgeLog()
    {
        control.purgeLog();
    }

    /**
     * records the fitness log of the best value found
     * 
     * @param generation
     *            generation to be logged
     * @param bestVal
     *            best total fitness value found
     * @param bestVals
     *            component values of the best fitness found
     */
    public void recordFitnessLog(int generation, double bestVal, double[] bestVals)
    {
        control.recordFitnessLog(generation, bestVal, bestVals);
    }

    /**
     * records the smell log for a generation
     * 
     * @param generation
     *            generation to be logged
     * @param smell
     *            best total smell
     * @param smells
     *            component values of the best smell
     */
    public void recordSmellLog(int generation, double smell, double[] smells)
    {
        control.recordSmellLog(generation, smell, smells);
    }
}
